使用 Helm 与 Kustomize 实践声明式管理
Kubernetes 于 2014 年夏季登陆 GitHub ,2015 年夏季推出 Kubernetes 的第一个 1.0 版本,其发展速度和进步在许多人的脑海中仍然记忆犹新。
像任何应用基础设施一样,workloads 的操作开始在 Kubernetes 上变得越来越重要。所以 Kubernetes 提出了 resources 的概念,通常由基于 YAML 的 manifests 来描述各种各样的 workloads,如 deployment.yaml。
随着需要部署的 manifests 数量和需要维护的 Kubernetes 集群数量的双重增加,我们需要更高效的 manifests 管理方式,以便在 cluster-to-cluster 之间获得更容易的部署,和更灵活的配置。
本文将介绍如何使用 Heml 和 Kustomize 来对 Kubernetes 生态系统内的对象进行声明式配置和包管理。
什么是 Helm ?
Helm 是 Kubernetes 的应用程序包管理器,您可使用 Helm Charts 描述应用程序的结构,使用 Helm 命令行界面,您可以回滚部署、监控应用程序的状态并跟踪每项部署的历史记录。
Helm 最初由Deis于 2015 年开发,并在首届 Kubecon 上展出。2019 年 4 月,CNCF 宣布 Helm 的孵化期结束,成为一个完整的项目。
Tiller是 Helm v2 平台的一部分。它来自与 Google 的 Deployment Manager 项目集成的部分,旨在成为一个工作运行器,与Cloud Foundry 的 Bosh没有太大区别。简而言之,Tiller 是 Helm 的集群内部分,它运行 Helm 的命令和图表。由于 Tiller 可以不受限制地访问 Kubernetes 集群成为集群安全的痛点,因此在 Helm V3 中将其删除了。
基本概念
- Chart:Helm 使用的包格式称为 chart。 chart 就是一个描述Kubernetes相关资源的文件集合。
- Release:在 Kubernetes 集群上运行的 Chart 的一个实例。在同一个集群上,一个 Chart 可以安装很多次。每次安装都会创建一个新的 release。
- Repository:用于发布和存储 Chart 的存储库。
Helm Charts
- Helm 图表在创建时必须具有遵循语义版本控制的版本号。
- Helm Charts 可以引用其他 Charts 作为依赖项,这是任何包管理器的核心。
- Helm Charts 的更多高级功能如Chart Hooks和Chart Tests,它们允许与 Release 的生命周期进行交互,以及分别针对 Chart 运行命令/测试的能力。
运行 helm create eg-Helm
创建一个 Helm Charts
可以看到 Chart 的基本目录结构包括:
eg-Helm/ # chart 包目录名
├── charts # 包含 chart 依赖的其他 chart
├── Chart.yaml # 包含了chart信息的YAML文件,如chart的名字,版本号信息。
├── crds # 自定义资源的定义
├── templates # 模板目录,和 values 结合时,可生成有效的 Kubernetes manifest
│ ├── deployment.yaml
│ ├── _helpers.tpl # helm视为公共库定义文件,主要用于定义通用的子模版、函数等
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt # 帮助信息文件,helm install 安装成功后会输出帮助信息
│ ├── serviceaccount.yaml
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml # chart 默认的配置值,模版可以引用这里参数。
简单示例
下面给出了 deployment、service、ingress 三个配置文件。
- Deployment
- Service
- Ingress
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: myapp #deployment应用名
labels:
app: myapp #deployment应用标签定义
spec:
replicas: 1 #pod副本数
selector:
matchLabels:
app: myapp #pod选择器标签
template:
metadata:
labels:
app: myapp #pod标签定义
spec:
containers:
- name: myapp #容器名
image: xxxxxx:1.7.9 #镜像地址
ports:
- name: http
containerPort: 80
protocol: TCP
apiVersion: v1
kind: Service
metadata:
name: myapp-svc #服务名
spec:
selector: #pod选择器定义
app: myapp
ports:
- protocol: TCP
port: 80
targetPort: 80
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: myapp-ingress #ingress应用名
spec:
rules:
- host: www.xxxxx.com #域名
http:
paths:
- path: /
backend:
serviceName: myapp-svc #服务名
servicePort: 80
提取 k8s 应用部署配置文件中的参数,作为 chart 包参数。
{{ }}
两个花括号包裹的内容为模版表达式。
- Deployment
- Service
- Ingress
- values
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: {{ .Release.Name }} #deployment应用名
labels:
app: {{ .Release.Name }} #deployment应用标签定义
spec:
replicas: {{ .Values.replicas}} #pod副本数
selector:
matchLabels:
app: {{ .Release.Name }} #pod选择器标签
template:
metadata:
labels:
app: {{ .Release.Name }} #pod标签定义
spec:
containers:
- name: {{ .Release.Name }} #容器名
image: {{ .Values.image }}:{{ .Values.imageTag }} #镜像地址
ports:
- name: http
containerPort: 80
protocol: TCP
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-svc #服务名
spec:
selector: #pod选择器定义
app: {{ .Release.Name }}
ports:
- protocol: TCP
port: 80
targetPort: 80
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: {{ .Release.Name }}-ingress #ingress应用名
spec:
rules:
- host: {{ .Values.host }} #域名
http:
paths:
- path: /
backend:
serviceName: {{ .Release.Name }}-svc #服务名
servicePort: 80
#域名
host: www.XXX.com
#镜像参数
image: XXXXXXXXXXXXXXXXXX
imageTag: 1.7.9
#pod 副本数
replicas:1
Helm 模版语法
表达式
- 模版表达式:
{{ 模版表达式 }}
{{- 模版表达式 -}}
表示去掉表达式输出结果前面和后面的空格{{- 模版表达式 }}
表示去掉前面空格{{ 模版表达式 -}}
表示去掉后面空格
变量
默认情况点( . ), 代表全局作用域,用于引用全局对象。
helm 全局作用域中有三个重要的全局对象:Values 、 Release 和 自定义模版变量
-
Values 代表的就是 values.yaml 定义的参数,通过.Values可以引用任意参数。
例如:
{{ .Values.images.repository }}
引用了全局作用域下的 Values 中的 image 对象的 repository。values.yamlimages:
repository: gcr.io -
Release 作为 Helm 内置变量为我们提供了如下的属性字段:
Release.Name # release 名字,一般通过Chart.yaml定义,或者通过helm命令在安装应用的时候指定。
Release.Time # release 安装时间
Release.Namespace # 命名空间
Release.Revision # release 版本号,是一个递增值,每次更新都会加一
Release.IsUpgrade # true 代表,当前release是一次更新
Release.IsInstall # true 代表,当前release是一次安装 -
自定义模版变量
自定义模版变量名以 $ 开头, 赋值运算符为
:=
例如:
{{- $relname := .Release.Name -}}
函数 & 管道运算符
Go templates 为我们提供了很多好用的函数,详见文档
-
调用函数的语法:
{{ functionName arg1 arg2... }}
-
管道(pipelines)运算符
|
,用于将模版输出的结果传递给下一个函数处理。例子如下:
-
将.Values.favorite.food 传递给 quote 函数处理,然后在输出结果:
{{ .Values.favorite.food | quote }}
-
先将.Values.favorite.food的值传递给upper函数将字符转换成大写,然后专递给quote加上引号包括起来:
{{ .Values.favorite.food | upper | quote }}
-
如果.Values.favorite.food为空,则使用default定义的默认值:
{{ .Values.favorite.food | default "默认值" }}
-
将.Values.favorite.food输出5次:
{{ .Values.favorite.food | repeat 5 }}
-
对输出结果缩进2个空格:
{{ .Values.favorite.food | nindent 2 }}
- 常用的关系运算符
>
、>=
、<
、!=
、与或非在 helm 模版中都以函数的形式实现。
关系运算函数定义:
- eq 相当于
=
- ne 相当于
!=
- lt 相当于
<=
- gt 相当于
>=
- and 相当于
&&
- or 相当于
||
- not 相当于
!
{{ if and .Values.fooString (eq .Values.fooString "foo") }}
{{ ... }}
{{ end }}
// 相当于 if (.Values.fooString && (.Values.fooString == "foo"))
流程控制语句
- if/else
{{ if 条件表达式 }}
# Do something
{{ else if 条件表达式 }}
# Do something else
{{ else }}
# Default case
{{ end }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
data:
myvalue: "Hello World"
drink: {{ .Values.favorite.drink | default "tea" | quote }}
food: {{ .Values.favorite.food | upper | quote }}
{{if eq .Values.favorite.drink "coffee"}}
mug: true
{{end}}
- with
with主要就是用来修改 . 作用域的,默认 . 代表全局作用域,with语句可以修改.的含义.
{{ with 引用的对象 }}
这里可以使用 . (点), 直接引用with指定的对象
{{ end }}
// .Values.favorite 是一个 object 类型
{{- with .Values.favorite }}
// .drink 相当于.Values.favorite.drink
drink: {{ .drink | default "tea" | quote }}
food: {{ .food | upper | quote }}
{{- end }}
如果需要在 with 作用域内引用全局对象,可以使用 $
指向根的上下文,当在一个范围内循环时会很有用。
例如:{{ $.Chart.Name }}
- range
range主要用于循环遍历数组类型。
遍历map类型,用于遍历键值对象,变量key代表对象的属性名,val代表属性值
{{- range key,val := 键值对象 }}
{{ $key }}: {{ $val | quote }}
{{- end}}
{{- range 数组 }}
{{ . | title | quote }} // `.` 引用数组元素值。
{{- end }}
#map类型
favorite:
drink: coffee
food: pizza
#数组类型
pizzaToppings:
- mushrooms
- cheese
- peppers
- onions
// map类型遍历例子:
{{- range $key, $val := .Values.favorite }}
{{ $key }}: {{ $val | quote }}
{{- end}}
// 数组类型遍历例子:
{{- range .Values.pizzaToppings}}
{{ . | quote }}
{{- end}}
子模版定义
_helpers.tpl 方法允许开发者在模板中使用字符串作为模板。将模板字符串作为值传给chart或渲染额外的配置文件时会很有用。
helm create默认为我们创建了_helpers.tpl 公共库定义文件,可以直接在里面定义子模版,也可以新建一个,只要以下划线开头命名即可。
// 定义模版
{{ define "模版名字" }} 模版内容 {{ end }}
// 引用模版:
{{ include "模版名字" 作用域}}
// 模版定义
{{- define "mychart.app" -}}
app_name: {{ .Chart.Name }}
app_version: "{{ .Chart.Version }}+{{ .Release.Time.Seconds }}"
{{- end -}}
// 引用模版:
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-configmap
labels:
{{ include "mychart.app" . | nindent 4 }} // 引用mychart.app模版内容,并对输出结果缩进4个空格
data:
myvalue: "Hello World"
模板调试
以下命令有助于你的调试:
helm lint # 验证chart是否遵循最佳实践的首选工具
helm install --dry-run --debug # 或者 helm template --debug 仅渲染模板。
helm get manifest RELEASE_NAME [flags] # 这是查看服务器上安装了哪些模板的好方法。
什么是 Kustomize?
Kustomize是 Kubernetes 生态系统的配置管理工具。与 Kubernetes 本身一样,Kustomize 是作为 Google 的一个开源项目开发的——因此从 Kubernetes 1.14 开始,Kustomize 作为默认 Kubernetes 命令行工具 kubectl 的一部分直接集成到 k8s 中也就不足为奇了。
正如它的名字,Kustomize 侧重于定制,你可以轻松地对 Kubernetes manifests 文件进行 patch(根据文件中定义的逻辑进行重构/替换)。
eg-Kustomize/
├── base
│ ├── kustomization.yaml
│ ├── namespace.yaml
│ ├── networkpolicy.yaml
│ ├── rolebinding.yaml
│ └── role.yaml
└── overlays
├── team-a
│ └── kustomization.yaml
├── team-b
│ └── kustomization.yaml
└── team-c
└── kustomization.yaml
例如,如果您想为您的开发和生产环境指定不同的补丁,您只需在 YAML 文件中指定这些更改,这些文件位于它们自己的目录中,组织在位于项目树中公共"base"旁边的"overlays"目录中.
3 种 patch 方法
- patches/patchesJson6902
- 用于指定 kubernetes 资源的“目标”属性,“路径”属性指定资源中的哪个属性被修改、添加或删除。
- patchJson6902是一个较旧的关键字,它只能通过target(无通配符)匹配一个资源,并且只接受 Group-version-kind (GVK)、命名空间和名称。
- patchesStrategicMerge
- 需要 kubernetes 资源的重复结构来标识正在修补的基础资源,然后是规范的修改部分以表示更改(或删除)的内容。
输入以下命令即可将它们有选择地应用于正在运行的集群:
kustomize build ~/app-project/overlays/development | kubectl apply -f -
# 或者
kubectl apply -k ~/app-project/overlays/development
# 查看渲染结果
kubectl kustomize .
利用 Kustomize 插件 ChartInflator 渲染 Helm Charts
实际使用 helm 时,我们经常的要对上游的 Helm Chart 包做一些定制化的 patch ,这些 patch 通常受限于定制化场景下,不能直接贡献给上游仓库,通过 fork 来维护自己的定制化修改后的 helm chart 包十分麻烦。这个时候我们可以利用 Kustomize 来对现有的 Helm Chart 打 patch。
安装 ChartInflator 插件:
chartinflator_dir="./kustomize/plugin/kustomize.config.k8s.io/v1/chartinflator"
mkdir -p ${chartinflator_dir}
curl -L https://raw.githubusercontent.com/kubernetes-sigs/kustomize/kustomize/v3.8.2/plugin/someteam.example.com/v1/chartinflator/ChartInflator > ${chartinflator_dir}/ChartInflator
chmod u+x ${chartinflator_dir}/ChartInflator
创建 ChartInflator 资源清单和 Helm 的 values.yaml 值文件:
cat << EOF >> chartinflator.yaml
apiVersion: kustomize.config.k8s.io/v1
kind: ChartInflator
metadata:
name: vault-official-helm-chart
chartRepo: https://helm.releases.hashicorp.com
chartName: vault
chartRelease: hashicorp
chartVersion: 0.7.0
releaseName: vault
values: values.yaml
EOF
helm repo add hashicorp https://helm.releases.hashicorp.com
helm show values --version 0.23.0 hashicorp/vault > values.yaml
# kustomize init 创建 Kustomize 文件
# kustomize edit add label env:dev 为所有资源添加一个 label 标签
cat << EOF >> kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generators:
- chartinflator.yaml
commonLabels:
env: dev
EOF
├── chartinflator.yaml
├── kustomization.yaml
├── kustomize
│ └── plugin
│ └── kustomize.config.k8s.io
│ └── v1
│ └── chartinflator
│ └── ChartInflator
└── values.yaml
渲染 Chart 模板
export KUSTOMIZE_PLUGIN_HOME="./kustomize/plugin/"
kustomize build --enable-alpha-plugins .
正常渲染完成后我们可以看到所有的资源上都被添加了一个 env: dev 的标签,这是实时完成的,不需要维护任何额外的文件的。
还有哪些其他 Kubernetes 模板工具?
- Jsonnet/Ksonnnet
- Skaffold